package com.bioscript.standalone.imps;

import java.io.FileReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.bioscript.standalone.UtilMethods;
import com.bioscript.standalone.IO.FileWriter;
import com.bioscript.standalone.IO.HitsFileParser;
import com.bioscript.standalone.IO.HitsParsingEvents;
import com.bioscript.standalone.IO.HitsParsingEvents.HitsParsingEventTypes;
import com.bioscript.standalone.dataStructures.BlastHit;
import com.bioscript.standalone.dataStructures.Range;
import com.bioscript.standalone.imps.BlastFileFilter.HitProperties;

public class BlastHitsProcessor {
	
	private List<BlastHit> hits = null;
	
	public List<BlastHit> getHits() {
		return new ArrayList<BlastHit>(hits);
	}

	public static void main(String[] args)
	{
		BlastHitsProcessor blastHitsProcessor = new BlastHitsProcessor();
		try 
		{
			Double identity = Double.parseDouble(args[6]);
			// read the tab delimited of all genes
			List<Range> allGenes = Range.getRangeList
			(com.bioscript.standalone.IO.FileReader.readFromFile(args[2]));
			
			blastHitsProcessor.processHits(args[0],Double.parseDouble(args[6]), allGenes);
			
			Map<HitProperties, Double> map = UtilMethods.newHashMap();
			map.put(HitProperties.identity, identity);
			BlastFileFilter fileFilter = new BlastFileFilter(map);
			blastHitsProcessor.hits = fileFilter.filter(blastHitsProcessor.hits);
			if (identity == 100)
			{
				blastHitsProcessor.filterByFeatureLength(allGenes);
			}
			// read the tab delimited gaps file
			List<Range> gaps = Range.getRangeList(com.bioscript.standalone.IO.FileReader.readFromFile(args[1]));
			
			// club the hits based on genes
			Map<String,List<BlastHit>> clubbedHits = blastHitsProcessor.clubHitsBasedOnGenes();
			
			// names of genes that are found
			List<String> genesFound = new ArrayList<String>(clubbedHits.keySet());
			FileWriter.writeToFile(args[3], genesFound);
			
			List<Range> genesInGaps = new ArrayList<Range>(); 
			// now all genes must include genes that are either in the gaps or have disappeared
			
			if (removeGenes(allGenes, genesFound))
			{
				for (Range r : allGenes)
				{
					for (Range g : gaps)
					{
						if (r.overLap(g))
						{
							genesInGaps.add(r);
							break;
						}
					}
				}
			}
			FileWriter.writeToFile(args[4], Range.getStringList(genesInGaps));
			allGenes.removeAll(genesInGaps);
			// genes that have disappeared
			FileWriter.writeToFile(args[5], Range.getStringList(allGenes));
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	private static boolean removeGenes(List<Range> allGenes,
			List<String> genesFound) {
		Iterator<Range> itr = allGenes.iterator();
		while(itr.hasNext())
		{
			Range r = itr.next();
			if (genesFound.contains(r.getName()))
			{
				itr.remove();
			}
		}
		return true;
	}

	public void processHits(String fileName, double identity, List<Range> allGenes) throws Exception
	{
		HitsFileParser<List<BlastHit>> fileParser =
			new HitsFileParser<List<BlastHit>>() {
			@Override
			public void handleEvent(HitsParsingEvents event) {
				if (event.getEventType() instanceof HitsParsingEventTypes)
				{
					switch((HitsParsingEvents.HitsParsingEventTypes)event.getEventType())
					{
					case comment:
						break;
					case entry:
						String readLine = (String) event.getSource();
						readLine = readLine.trim();
						getValue().add(BlastHit.getNewInstance(readLine));
					}
				}	
			}
		};
		hits = new ArrayList<BlastHit>();
		fileParser.setValue(hits);
		fileParser.parse(new FileReader(fileName));
		
//		Map<HitProperties, Double> map = UtilMethods.newHashMap();
//		map.put(HitProperties.identity, identity);
//		BlastFileFilter fileFilter = new BlastFileFilter(map);
//		hits = fileFilter.filter(hits);
//		if (identity == 100)
//		{
//			filterByFeatureLength(allGenes);
//		}
	}

	private void filterByFeatureLength(List<Range> allGenes) {
		List<BlastHit> hits_temp = new ArrayList<BlastHit>();
		for (BlastHit blastHit : hits)
		{
			String name = blastHit.getQuery().getName();
			int alignmentLength = blastHit.getAlignmentLength();
			for (Range range : allGenes)
			{
				if (range.getName().equalsIgnoreCase(name))
				{
					if (range.getLength() == alignmentLength)
					{
						hits_temp.add(blastHit);
						break;
					}
				}
			}
		}
		hits = hits_temp;
	}

	public List<BlastHit> reFilter(double identity, List<BlastHit> hits)
	{
		Map<HitProperties, Double> map = UtilMethods.newHashMap();
		map.put(HitProperties.identity, identity);
		BlastFileFilter fileFilter = new BlastFileFilter(map);
		return fileFilter.filter(hits);
	}
	
	public Map<String,List<BlastHit>> clubHitsBasedOnGenes() 
	{
		Map<String,List<BlastHit>> map = UtilMethods.newHashMap();
		for (BlastHit hit : hits)
		{
			String gene = hit.getQuery().getName();
			List<BlastHit> lst = null;
			if (map.containsKey(gene))
			{
				lst = map.get(gene);
			}
			else
				lst = new ArrayList<BlastHit>();
			lst.add(hit);
			map.put(gene, lst);
		}
		return map;
	}
}

class BlastFileFilter {
	
	public static enum HitProperties
	{
		identity,
		mismatches,
		gapopenings,
		eval;
	}
	private Map<HitProperties,Double> map = null;
	
	public BlastFileFilter(Map<HitProperties,Double> map)
	{
		this.map = map;
	}
	
	public List<BlastHit> filter(List<BlastHit> hits)
	{
		if (map == null || map.keySet().size() == 0) return hits;
		else
		{
			List<BlastHit> filtered = new ArrayList<BlastHit>();
			Set<HitProperties> set = map.keySet();
			for(BlastHit hit : hits)
			{
				boolean include = true;
				for (HitProperties prop : set)
				{
					switch(prop)
					{
					case eval:
						if (hit.geteVal() >= map.get(prop))
							include = false;	
						break;
					case gapopenings:
						if (hit.getGapOpenings() <= map.get(prop))
							include = false;
						break;
					case identity:
						if (hit.getIdentity() < map.get(prop))
							include = false;
						break;
					case mismatches:
						if (hit.getMismatches() <= map.get(prop))
							include = false;
						break;
					}
					if (!include) break;
				}
				if (include)
					filtered.add(hit);
			}
			return filtered;
		}
	}
}
